#include "Renderer/perspectiveCamera.h"

#include <glm/gtc/matrix_transform.hpp>
#include "perspectiveCamera.h"

namespace CC
{

    PerspectiveCamera::PerspectiveCamera(const glm::mat4 projection)
        : Camera(projection)
    {
    }
    PerspectiveCamera::PerspectiveCamera(float fovy, float aspect, float zNear, float zFar)
        : m_ViewMatrix(1.0f), Camera(glm::perspective(glm::radians(fovy), aspect, zNear, zFar)), m_fovy(fovy), m_aspect(aspect), m_zNear(zNear), m_zFar(zFar), m_Front(glm::vec3(0.0f, 0.0f, -1.0f)), m_Up(glm::vec3(0.0f, 1.0f, 0.0f)), m_Right(glm::vec3(1.0f, 0.0f, 0.0f)), m_WorldUp(m_Up), m_Yaw(270.0f), m_Pitch(0.0f)
    {
        RecalculateViewMatrix();
    }

    PerspectiveCamera::~PerspectiveCamera()
    {
    }

    void PerspectiveCamera::OnUpdate(Timestep ts)
    {
        RecalculateViewMatrix();
    }

    void PerspectiveCamera::OnEvent(Event &e)
    {
        EventDispatcher dispatcher(e);
        dispatcher.Dispatch<WindowResizeEvent>(std::bind(&PerspectiveCamera::OnWindowResize, this, std::placeholders::_1));
    }

    void PerspectiveCamera::SetProjection(float fovy, float aspect, float zNear, float zFar)
    {
        m_aspect = aspect;
        glm::mat4 m = glm::perspective(glm::radians(fovy), aspect, zNear, zFar);
        Camera::SetProjection(m);
        RecalculateViewMatrix();
    }

    void PerspectiveCamera::OnWindowResize(WindowResizeEvent &e)
    {
        if ((float)e.GetWidth() == 0 || (float)e.GetHeight() == 0)
            return;
        m_aspect = (float)e.GetWidth() / (float)e.GetHeight();
        SetProjection(m_fovy, m_aspect, m_zNear, m_zFar);
    }

    void PerspectiveCamera::SetAspect(float a)
    {
        m_aspect = a;
        SetProjection(m_fovy, m_aspect, m_zNear, m_zFar);
    }

    void PerspectiveCamera::RecalculateViewMatrix()
    {
        glm::vec3 front;

        if (m_HasLookAt)
        {
            m_ViewMatrix = glm::lookAt(m_Position, m_TargetPosition, m_WorldUp);
            /* front = glm::normalize(m_TargetPosition - m_Position);
             m_Front = glm::normalize(front);
             m_Right = glm::normalize(glm::cross(m_Front, m_WorldUp));
             m_Up = glm::normalize(glm::cross(m_Right, m_Front));

             m_Pitch = glm::degrees(asin(front.y));

             float v = cos(glm::radians(m_Pitch));
             if (v != 0.0) {
                 m_Yaw = glm::degrees(acos(front.x / v));
             }
             else {
                 m_Yaw = glm::degrees(acos(front.x));
             }       */
        }
        else
        {
            front.x = cos(glm::radians(m_Yaw)) * cos(glm::radians(m_Pitch));
            front.y = sin(glm::radians(m_Pitch));
            front.z = sin(glm::radians(m_Yaw)) * cos(glm::radians(m_Pitch));
            m_Front = glm::normalize(front);
            // also re-calculate the Right and Up vector
            m_Right = glm::normalize(glm::cross(m_Front, m_WorldUp));
            m_Up = glm::normalize(glm::cross(m_Right, m_Front));

            // CC_CORE_INFO("m_Up %f %f %f\n", m_Up[0], m_Up[1], m_Up[2]);

            m_TargetPosition = m_Position + m_Front;

            m_ViewMatrix = glm::lookAt(m_Position, m_Position + m_Front, m_Up);
        }

        m_ViewProjectionMatrix = GetProjection() * m_ViewMatrix;
    }
}